﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace CPPPlugin
{
    /// <summary>
    /// C++ highlighter
    /// </summary>
    public class CPPPlugin : Interface.IPlugin
    {
        private string[] keywords = { "if", "else", "switch", "case", "default", "break", "goto", "return",
                                       "for", "while", "do", "continue", "typedef", "sizeof", "NULL", "new", "delete",
                                       "throw", "try", "catch", "namespace", "operator", "this", "const_cast", "static_cast",
                                       "dynamic_cast", "reinterpret_cast", "true", "false", "using", "typeid", "and", "and_eq",
                                       "bitand", "bitor", "compl", "not", "not_eq", "or", "or_eq", "xor", "xor_eq",
                                       "void", "struct", "union", "enum", "char", "short", "int", "long", "double",
                                       "float", "signed", "unsigned", "const", "static", "extern", "auto", "register",
                                       "volatile", "bool", "class", "private", "protected", "public", "friend", "inline",
                                       "template", "virtual", "asm", "explicit", "typename", "mutable",
                                       "a", "addindex", "addtogroup", "anchor", "arg", "attention", "author", "b", "brief",
                                       "bug", "c", "class", "code", "date", "def", "defgroup", "deprecated", "dontinclude",
                                       "e", "em", "endcode", "endhtmlonly",  "endlatexonly", "endlink", "endverbatim",
                                       "enum", "example", "exception", "f$", "f[", "f]", "file", "fn", "hideinitializer",
                                       "htmlinclude", "htmlonly", "if", "image", "ingroup", "internal", "invariant",
                                       "interface", "latexonly", "li", "line", "link", "mainpage", "name", "namespace", "nosubgrouping",
                                       "note", "overload", "p", "page", "par", "param", "post", "preref", "relates", "remarks",
                                       "return", "retval", "sa", "section", "see", "showinitializer", "since", "skip", "skipline",
                                       "struct", "subsection", "test", "throw", "throws", "todo", "typedef", "union", "until", "var",
                                       "verbatim", "verbinclude", "version", "warning", "weakgroup", "$", "@",
                                       "&amp;", "&lt;", "&gt;"};

        private string[] preprocessors = {
                                             "#ifndef", "#define", "#include", "#endif", "#error", "#import", "#undef",
                                             "#elif", "#if", "#include", "#using", "#else", "#ifdef", "#line", "#pragma"
                                         };
        /// <summary>
        /// Plugin name
        /// </summary>
        public string Name
        {
            get
            {
                return "Sourcecode2HTML Plugin for *.cpp (C++)";
            }
        }
        /// <summary>
        /// Plugin version
        /// </summary>
        public string Version
        {
            get
            {
                return "0.1";
            }
        }
        /// <summary>
        /// Author
        /// </summary>
        public string Author
        {
            get
            {
                return "Евгений Кукло, Евгений Казбан, Власов Иван";
            }
        }
        /// <summary>
        /// Language name
        /// </summary>
        public string LanguageName
        {
            get
            {
                return "C++";
            }
        }

        /// <summary>
        /// Extension
        /// </summary>
        public string Extension
        {
            get
            {
                return "cpp";
            }
        }

        /// <summary>
        /// Converts source code to HTML string
        /// </summary>
        /// <param name="input">Input string</param>
        /// <param name="linenumbers">Draw line numbers</param>
        /// <returns>Returns HTML string</returns>
        public string HighlightKeywords(string input, bool linenumbers)
        {

            bool CaseSensitive = true;
            string CommentRegEx = @"/\*.*?\*/|//.*?(?=\r|\n)";
            string StringRegEx = @"@?""""|@?"".*?(?!\\).""|''|'.*?(?!\\).'";
            string Keywords = "";
            string Preprocessors = "";
            for (int k = 0; k < keywords.Length; k++)
                Keywords += keywords[k] + " ";
            for (int j = 0; j < preprocessors.Length; j++)
                Preprocessors += preprocessors[j] + " ";
            Preprocessors = Preprocessors.Trim();
            Keywords = Keywords.Trim();
            Regex r;
            Regex codeRegex;
            r = new Regex(@"\w+|-\w+|#\w+|@@\w+|#(?:\\(?:s|w)(?:\*|\+)?\w+)+|@\\w\*+");
            string regKeyword = r.Replace(Keywords, @"(?<=^|\W)$0(?=\W)");
            string regPreproc = r.Replace(Preprocessors, @"(?<=^|\s)$0(?=\s|$)");
            r = new Regex(@" +");
            regKeyword = r.Replace(regKeyword, @"|");
            regPreproc = r.Replace(regPreproc, @"|");

            if (regPreproc.Length == 0)
                regPreproc = "(?!.*)_{37}(?<!.*)";

            StringBuilder regAll = new StringBuilder();
            regAll.Append("(");
            regAll.Append(CommentRegEx);
            regAll.Append(")|(");
            regAll.Append(StringRegEx);
            if (regPreproc.Length > 0)
            {
                regAll.Append(")|(");
                regAll.Append(regPreproc);
            }
            regAll.Append(")|(");
            regAll.Append(regKeyword);
            regAll.Append(")");


            RegexOptions caseInsensitive = CaseSensitive ? 0 : RegexOptions.IgnoreCase;
            codeRegex = new Regex(regAll.ToString(), RegexOptions.Singleline | caseInsensitive);

            StringBuilder sb = new StringBuilder(input);
            sb.Replace("&", "&amp;");
            sb.Replace("<", "&lt;");
            sb.Replace(">", "&gt;");
            sb.Replace("\t", string.Empty.PadRight(4));
            input = codeRegex.Replace(sb.ToString(), new MatchEvaluator(this.MatchEval));
            sb = new StringBuilder();

            sb.Append("<div class=\"csharpcode\">\n");
            StringReader reader = new StringReader(input);
            int i = 0;
            string spaces = "    ";
            int order;
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                i++;
                if ((i % 2) == 1)
                {
                    sb.Append("<pre class=\"alt\">");
                }
                else
                {
                    sb.Append("<pre>");
                }
                if (linenumbers)
                {
                    order = (int)Math.Log10(i);
                    sb.Append("<span class=\"lnum\">"
                        + spaces.Substring(0, 3 - order) + i.ToString()
                        + ":  </span>");
                }
                if (line.Length == 0)
                    sb.Append("&nbsp;");
                else
                    sb.Append(line);
                sb.Append("</pre>\n");
            }
            reader.Close();
            sb.Append("</div>");

            return sb.ToString();
        }

        /// <summary>
        /// Check matches and convert to HTML tags
        /// </summary>
        /// <param name="match">Match</param>
        /// <returns>Converted string</returns>
        protected string MatchEval(Match match)
        {
            if (match.Groups[1].Success)
            {
                StringReader reader = new StringReader(match.ToString());
                string line;
                StringBuilder sb = new StringBuilder();
                while ((line = reader.ReadLine()) != null)
                {
                    if (sb.Length > 0)
                    {
                        sb.Append("\n");
                    }
                    sb.Append("<span class=\"rem\">");
                    sb.Append(line);
                    sb.Append("</span>");
                }
                return sb.ToString();
            }
            if (match.Groups[2].Success)
            {
                return "<span class=\"str\">" + match.ToString() + "</span>";
            }
            if (match.Groups[3].Success)
            {
                return "<span class=\"preproc\">" + match.ToString() + "</span>";
            }
            if (match.Groups[4].Success)
            {
                return "<span class=\"kwrd\">" + match.ToString() + "</span>";
            }
            System.Diagnostics.Debug.Assert(false, "None of the above!");
            return "";
        }
    }
}